/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.wsinfra.deployment.impl;

import java.util.Enumeration;
import java.util.Hashtable;

import javax.servlet.ServletConfig;
import javax.servlet.UnavailableException;
import javax.xml.soap.SOAPMessage;

import org.mortbay.http.HttpContext;
import org.mortbay.http.HttpServer;
import org.mortbay.http.SocketListener;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.servlet.ServletHolder;

import cgl.narada.wsinfra.deployment.RoleExecutionEnvironment;
import cgl.narada.wsinfra.deployment.RoleRetriever;
import cgl.narada.wsinfra.deployment.SOAPServletContainer;
import cgl.narada.wsinfra.exception.DeploymentException;

/** This container will be used to register SOAP Based roles. It is expected 
    that in some cases there may be a servlet associated per role. Typically,
    this need not be the case. Upon the receipt of a SOAP message, a special
    class will parse the SOAP message to determine all the <i>targeted</i>
    roles. Depending on the roles, appropriate filter pipelines will be 
    deployed to ensure complete processing of a SOAP message. In some cases,
    a set of default roles may be associated with a given SOAP message. This
    may include processing related to logging every SOAP message etc.

    @author Shrideep Pallickara
    $Date: 2005/07/29 22:44:01 $
    $Revision: 1.1 $
 */ 

public class SOAPServletContainerImpl extends SOAPServletContainer {

  private static final SOAPServletContainer instance = 
  new SOAPServletContainerImpl();
  
  private int portNum = 0;
  private boolean initializedServices = false;
  private boolean servicesStarted = false;

  private Hashtable roleAndServlets;
  private Hashtable servletAndRoles;
  private Hashtable servlets;
  private Hashtable servletPaths;

  private HttpServer httpServer;
  private HttpContext httpContext;
  private RoleExecutionEnvironment roleExecutionEnvironment;
  private RoleRetriever roleRetriever;
  private String moduleName = "SOAPServletContainerImpl: ";
  
  private SOAPServletContainerImpl() {
    httpServer = new HttpServer();
    roleAndServlets = new Hashtable();
    servletAndRoles = new Hashtable();
    servlets = new Hashtable();
    servletPaths = new Hashtable();

    roleExecutionEnvironment = RoleExecutionEnvironment.getInstance();
    roleRetriever = RoleRetriever.getInstance();
  }

  
  public static SOAPServletContainer getInstance() {
    return instance;
  }



  /** Starts up the HTTP Servlet container at this port number. This method
   throws an exception */
  public synchronized void 
  initializeServices(int portNum) throws DeploymentException {
    checkPortNum(portNum);
    checkIfInitializedServices();
    this.portNum = portNum;

    SocketListener socketListener = new SocketListener();
    socketListener.setPort(portNum);
    httpServer.addListener(socketListener);

    try {
      httpContext = new HttpContext();
      httpContext.setContextPath("/");
      httpContext.setResourceBase("./docroot/");

      httpServer.addContext(httpContext);
     
      initializedServices = true;
    } catch (Exception ex) {
      throw new DeploymentException(moduleName + ex.toString());
    }
  }


  /** Start Services */
  public synchronized void startServices() throws DeploymentException {
    if (servicesStarted) {
      System.out.println(moduleName + "Services have ALREADY been started");
      return;
    }

    try {
      httpServer.start();
    } catch (Exception ex) {
      throw new DeploymentException(moduleName + ex.toString());
    }
  }


  /** Stop Services */
  public synchronized void stopServices() throws DeploymentException {
    if (!servicesStarted) {
      System.out.println(moduleName + 
			 "Services currently NOT started: no need to STOP.");
      return;
    }
    

    try {
      httpServer.stop();
    } catch (Exception ex) {
      throw new DeploymentException(moduleName + ex.toString());
    }
  }



  /** This method registers a servlet. The servlet is not associated with
      a specific role.*/
  public synchronized void 
  registerServlet(String servletName, String servletPath, String servletClass)
    throws DeploymentException {
    checkIfServletNameIsNull(servletName);
    checkIfPathSpecOrServletClassIsNull(servletPath, servletClass);
    checkIfServletAlreadyRegistered(servletName);
    checkIfServletPathAlreadyRegistered(servletPath);

    ServletHandler servletHandler = new ServletHandler();
    ServletHolder servletHolder = 
      servletHandler.addServlet(servletName, servletPath, servletClass);

    httpContext.addHandler(servletHandler);
    
    servlets.put(servletName, servletPath);
    servletPaths.put(servletPath, servletName);
  }




  /** This method deregisters a servlet. The servlet is not associated with
      a specific role.*/
  public synchronized void deregisterServlet(String servletName) 
    throws DeploymentException {
    checkIfServletNameIsNull(servletName);
    String errorReport = null;

    if (!servlets.containsKey(servletName)) {
      errorReport = moduleName + "The specified servlet (" + servletName + 
	") is an unknown one. No de-registration is possible.";
      throw new DeploymentException(errorReport);
    }

    if (servletAndRoles.containsKey(servletName)) {
      String role = (String) servletAndRoles.remove(servletName);
      deregisterServletForRole(servletName, role); 
      return;
    }
    
    String servletPath = (String) servlets.remove(servletName);
    servletPaths.remove(servletPath);
  }





  /** This method registers a servlet associated with a specific role. This 
      method throws an exception if another servlet has been previously 
      registered for this role or if there are problems deploying this 
      servlet.*/
  public synchronized void 
  registerServletForRole(String role, String servletName, 
			 String servletPath, String servletClass)
    throws DeploymentException {

    checkIfRoleIsNull(role);
    checkIfServletNameIsNull(servletName);
    checkIfPathSpecOrServletClassIsNull(servletPath, servletClass);
    
    checkIfRoleAlreadyRegistered(role);    

    checkIfServletAlreadyRegistered(servletName);
    checkIfServletPathAlreadyRegistered(servletPath);

    
    ServletHandler servletHandler = new ServletHandler();
    ServletHolder servletHolder = 
      servletHandler.addServlet(servletName, servletPath, servletClass);

    httpContext.addHandler(servletHandler);
    
    roleAndServlets.put(role, servletHolder);
    servletAndRoles.put(servletName, role);
    servlets.put(servletName, servletPath);
    servletPaths.put(servletPath, servletName);
  } 
  
  


  
  /** This method de-registers a servlet associated witha a specific role. This
      method throws an exception if there are no servlets registered for this
      role. */
  public synchronized void 
  deregisterServletForRole(String servletName, String role) 
    throws DeploymentException {
    
    checkIfRoleIsNull(role);
    checkIfServletNameIsNull(servletName);

    String errorReport = null;
    if (!roleAndServlets.containsKey(role)) {
      errorReport = moduleName + "The role  [" + role + 
	"] has not been previously registered.";
      throw new DeploymentException(errorReport);
    }

    String registeredServletName =  getServletNameForRole(role);
    
    if (servletName != registeredServletName) {
      errorReport = moduleName + "Registered servlet [" + registeredServletName
	+ "] DOES NOT match the one [" + servletName + 
	"] being submitted for removal.";
      throw new DeploymentException(errorReport);
    }

    servletAndRoles.remove(servletName);
    String servletPath = (String) servlets.remove(servletName);
    servletPaths.remove(servletPath);

    ServletHolder servletHolder = (ServletHolder) roleAndServlets.remove(role);

    try {
      servletHolder.getServlet().destroy();
    } catch (UnavailableException uaEx) {
      throw new DeploymentException(moduleName + uaEx);
    }
  }





  /** Checks to see if a servlet has been registered for the specified role. */
  public boolean isServletRegisteredForRole(String servletName, String role) 
    throws DeploymentException {
    checkIfRoleIsNull(role);
    checkIfServletNameIsNull(servletName);
    
    if (roleAndServlets.containsKey(role)) {
      return true;
    }
    return false;
  }





  /** Retrieve the ServletContext associated with a specific role. */
  public ServletConfig getServletConfigForRole(String role)
    throws DeploymentException {
    checkIfRoleIsNull(role);
    String errorReport = null;
    if (!roleAndServlets.containsKey(role)) {
      errorReport = moduleName + "The role  [" + role + 
	"] has not been previously registered.";
      throw new DeploymentException(errorReport);
    }

    ServletHolder servletHolder = (ServletHolder) roleAndServlets.remove(role);
    ServletConfig servletConfig = null;
    try {
      servletConfig = servletHolder.getServlet().getServletConfig();
      return servletConfig;
    } catch (UnavailableException uaEx) {
      throw new DeploymentException(moduleName + uaEx);
    }
  }





  /** Method used by servlets to notify about SOAP messages received over the
      network. */
  public void 
  processSOAPMessage(SOAPMessage soapMessage,
		     String servletName) throws DeploymentException {
     checkIfServletNameIsNull(servletName);
    /** The first step is to check to see if a role is associated with this
	servlet. If so processing is delegated to the role associated with this
	servlet. */
    
     if (servletAndRoles.containsKey(servletName)) {
       String role = (String) servletAndRoles.get(servletName);
       roleExecutionEnvironment.manageExecutionOfRole(role, soapMessage);
       return;
     }


    /** If there are no roles associated with the servlet, the roles associated
	with the SOAPMessage need to be computed. */
     
     Enumeration roles = roleRetriever.retrieveRoles(soapMessage);
     roleExecutionEnvironment.manageExecutionOfRoles(roles, soapMessage);
     
  }

  

  
  /** Gets the servlet name for a role. */
  private String getServletNameForRole(String role) {
    String servletName = null;
    
    if (roleAndServlets.containsKey(role) ) {
      ServletHolder holder = (ServletHolder) roleAndServlets.get(role);
      try {
	servletName = holder.getServlet().getServletConfig().getServletName();
      } catch (UnavailableException uaEx) {
	System.out.println(moduleName + 
			   "Problems retrieving servletName" + uaEx);
      }
    }
    return servletName;
  }
  
  

  /** Checks to see if the servlet has already been registered. */
  private void checkIfServletAlreadyRegistered(String servletName) 
    throws DeploymentException {
    
    if (servlets.containsKey(servletName)) {
      String servletPath = (String) servlets.get(servletName);
      String errorReport = moduleName + "The servlet [" + servletName + 
	"] has already been registered, with a servlet path of (" +
	servletPath + ")";
      throw new DeploymentException(errorReport);
    }

  }


  /** Checks to see if the servlet path has already been registered. */
  private void checkIfServletPathAlreadyRegistered(String servletPath) 
    throws DeploymentException {
    if (servletPaths.containsKey(servletPath)) {
      String servletName = (String) servlets.get(servletPath);
      String errorReport = moduleName + "The servlet [" + servletName + 
	"] has already been registered, with a servlet path of (" +
	servletPath + ")";
      throw new DeploymentException(errorReport);
    }

  }




  
  /** Checks to see if a role has been previously registered. */
  private void checkIfRoleAlreadyRegistered(String role) 
    throws DeploymentException {

    if (roleAndServlets.containsKey(role)) {
      String servletName = getServletNameForRole(role);
      String errorReport = moduleName + "The servlet [" + servletName + 
	"] has already been registered for this role.";
      throw new DeploymentException(errorReport);
    }
  }




  /** Check to see if the port num is a valid one. */
  private void checkPortNum(int portNum) throws DeploymentException {
    if (portNum == 0) {
      String errorReport = moduleName + "The specified port [" + portNum +
	"] is an invalid one!";
      throw new DeploymentException(errorReport);
    }
  }




  /** Check to see if the services have been initialized. */
  private void checkIfInitializedServices() throws DeploymentException {
    if (initializedServices) {
      String errorReport = moduleName + "Services have already been " +
	"initialized on port [" + portNum + "]";
      throw new DeploymentException(errorReport);
    }
  }





  /** Checks to see if the role is NULL. */
  private void checkIfRoleIsNull(String role) throws DeploymentException {
    if (role == null) {
      throw new DeploymentException(moduleName + 
				    "The specified role is NULL!");
    }    
  }




  /** Checks to see if the role is NULL. */
  private void checkIfServletNameIsNull(String servletName) 
    throws DeploymentException {
    if (servletName == null) {
      throw new DeploymentException(moduleName + 
				    "The specified servletName is NULL!");
    }    
  }




  /** Checks to see if the path specification or the servlet class name 
      is NULL. */
  private void 
  checkIfPathSpecOrServletClassIsNull(String servletPath, String servletClass) 
  throws DeploymentException  {

    if (servletPath == null) {
      throw new DeploymentException(moduleName + 
				    "The servletPath specification is NULL!");
    }

    if (servletClass == null) {
      throw new DeploymentException(moduleName + 
				    "Specified servlet class Name is NULL!");
    }
  }
  
  
}
